{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "d:\\Users\\MECHREVO\\anaconda3\\Lib\\site-packages\\torch\\utils\\_pytree.py:185: FutureWarning: optree is installed but the version is too old to support PyTorch Dynamo in C++ pytree. C++ pytree support is disabled. Please consider upgrading optree using `python3 -m pip install --upgrade 'optree>=0.13.0'`.\n",
      "  warnings.warn(\n"
     ]
    }
   ],
   "source": [
    "from graphviz import Digraph\n",
    "import math\n",
    "import torch\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# -*- coding: UTF-8 -*-\n",
    "'''\n",
    "此脚本用于定义Scalar类，以及相应的可视化工具\n",
    "'''\n",
    "\n",
    "\n",
    "from graphviz import Digraph\n",
    "import math\n",
    "\n",
    "\n",
    "class Scalar:\n",
    "    \n",
    "    def __init__(self, value, prevs=[], op=None, label='', requires_grad=True):\n",
    "        # 节点的值\n",
    "        self.value = value\n",
    "        # 节点的标识（label）和对应的运算（op），用于作图\n",
    "        self.label = label\n",
    "        self.op = op\n",
    "        # 节点的前节点，即当前节点是运算的结果，而前节点是参与运算的量\n",
    "        self.prevs = prevs\n",
    "        # 是否需要计算该节点偏导数，即∂loss/∂self（loss表示最后的模型损失）\n",
    "        self.requires_grad = requires_grad\n",
    "        # 该节点偏导数，即∂loss/∂self\n",
    "        self.grad = 0.0\n",
    "        # 如果该节点的prevs非空，存储所有的∂self/∂prev\n",
    "        self.grad_wrt = dict()\n",
    "        # 作图需要，实际上对计算没有作用\n",
    "        self.back_prop = dict()\n",
    "        \n",
    "    def __repr__(self):\n",
    "        return f'Scalar(value={self.value:.2f}, grad={self.grad:.2f})'\n",
    "    \n",
    "    def __add__(self, other):\n",
    "        '''\n",
    "        定义加法，self + other将触发该函数\n",
    "        '''\n",
    "        if not isinstance(other, Scalar):\n",
    "            other = Scalar(other, requires_grad=False)\n",
    "        # output = self + other\n",
    "        output = Scalar(self.value + other.value, [self, other], '+')\n",
    "        output.requires_grad = self.requires_grad or other.requires_grad\n",
    "        # 计算偏导数 ∂output/∂self = 1\n",
    "        output.grad_wrt[self] = 1\n",
    "        # 计算偏导数 ∂output/∂other = 1\n",
    "        output.grad_wrt[other] = 1\n",
    "        return output\n",
    "    \n",
    "    def __sub__(self, other):\n",
    "        '''\n",
    "        定义减法，self - other将触发该函数\n",
    "        '''\n",
    "        if not isinstance(other, Scalar):\n",
    "            other = Scalar(other, requires_grad=False)\n",
    "        # output = self - other\n",
    "        output = Scalar(self.value - other.value, [self, other], '-')\n",
    "        output.requires_grad = self.requires_grad or other.requires_grad\n",
    "        # 计算偏导数 ∂output/∂self = 1\n",
    "        output.grad_wrt[self] = 1\n",
    "        # 计算偏导数 ∂output/∂other = -1\n",
    "        output.grad_wrt[other] = -1\n",
    "        return output\n",
    "    \n",
    "    def __mul__(self, other):\n",
    "        '''\n",
    "        定义乘法，self * other将触发该函数\n",
    "        '''\n",
    "        if not isinstance(other, Scalar):\n",
    "            other = Scalar(other, requires_grad=False)\n",
    "        # output = self * other\n",
    "        output = Scalar(self.value * other.value, [self, other], '*')\n",
    "        output.requires_grad = self.requires_grad or other.requires_grad\n",
    "        # 计算偏导数 ∂output/∂self = other\n",
    "        output.grad_wrt[self] = other.value\n",
    "        # 计算偏导数 ∂output/∂other = self\n",
    "        output.grad_wrt[other] = self.value\n",
    "        return output\n",
    "    \n",
    "    def __pow__(self, other):\n",
    "        '''\n",
    "        定义乘方，self**other将触发该函数\n",
    "        '''\n",
    "        assert isinstance(other, (int, float))\n",
    "        # output = self ** other\n",
    "        output = Scalar(self.value ** other, [self], f'^{other}')\n",
    "        output.requires_grad = self.requires_grad\n",
    "        # 计算偏导数 ∂output/∂self = other * self**(other-1)\n",
    "        output.grad_wrt[self] = other * self.value**(other - 1)\n",
    "        return output\n",
    "    \n",
    "    def sigmoid(self):\n",
    "        '''\n",
    "        定义sigmoid\n",
    "        '''\n",
    "        s = 1 / (1 + math.exp(-1 * self.value))\n",
    "        output = Scalar(s, [self], 'sigmoid')\n",
    "        output.requires_grad = self.requires_grad\n",
    "        # 计算偏导数 ∂output/∂self = output * (1 - output)\n",
    "        output.grad_wrt[self] = s * (1 - s)\n",
    "        return output\n",
    "    \n",
    "    def __rsub__(self, other):\n",
    "        '''\n",
    "        定义右减法，other - self将触发该函数\n",
    "        '''\n",
    "        if not isinstance(other, Scalar):\n",
    "            other = Scalar(other, requires_grad=False)\n",
    "        output = Scalar(other.value - self.value, [self, other], '-')\n",
    "        output.requires_grad = self.requires_grad or other.requires_grad\n",
    "        # 计算偏导数 ∂output/∂self = -1\n",
    "        output.grad_wrt[self] = -1\n",
    "        # 计算偏导数 ∂output/∂other = 1\n",
    "        output.grad_wrt[other] = 1\n",
    "        return output\n",
    "    \n",
    "    def __radd__(self, other):\n",
    "        '''\n",
    "        定义右加法，other + self将触发该函数\n",
    "        '''\n",
    "        return self.__add__(other)\n",
    "    \n",
    "    def __rmul__(self, other):\n",
    "        '''\n",
    "        定义右乘法，other * self将触发该函数\n",
    "        '''\n",
    "        return self * other\n",
    "    \n",
    "    def backward(self, fn=None):\n",
    "        '''\n",
    "        由当前节点出发，求解以当前节点为顶点的计算图中每个节点的偏导数，i.e. ∂self/∂node\n",
    "        参数\n",
    "        ----\n",
    "        fn ：画图函数，如果该变量不等于None，则会返回向后传播每一步的计算的记录\n",
    "        返回\n",
    "        ----\n",
    "        re ：向后传播每一步的计算的记录\n",
    "        '''\n",
    "        def _topological_order():\n",
    "            '''\n",
    "            利用深度优先算法，返回计算图的拓扑排序（topological sorting）\n",
    "            '''\n",
    "            def _add_prevs(node):\n",
    "                if node not in visited:\n",
    "                    visited.add(node)\n",
    "                    for prev in node.prevs:\n",
    "                        _add_prevs(prev)\n",
    "                    ordered.append(node)\n",
    "            ordered, visited = [], set()\n",
    "            _add_prevs(self)\n",
    "            return ordered\n",
    "\n",
    "        def _compute_grad_of_prevs(node):\n",
    "            '''\n",
    "            由node节点出发，向后传播\n",
    "            '''\n",
    "            # 作图需要，实际上对计算没有作用\n",
    "            node.back_prop = dict()\n",
    "            # 得到当前节点在计算图中的梯度。由于一个节点可以在多个计算图中出现，\n",
    "            # 使用cg_grad记录当前计算图的梯度\n",
    "            dnode = cg_grad[node]\n",
    "            # 使用node.grad记录节点的累积梯度\n",
    "            node.grad += dnode\n",
    "            for prev in node.prevs:\n",
    "                # 由于node节点的偏导数已经计算完成，可以向后扩散（反向传播）\n",
    "                # 需要注意的是，向后扩散到上游节点是累加关系\n",
    "                grad_spread = dnode * node.grad_wrt[prev]\n",
    "                cg_grad[prev] = cg_grad.get(prev, 0.0) + grad_spread\n",
    "                node.back_prop[prev] = node.back_prop.get(prev, 0.0) + grad_spread\n",
    "        \n",
    "        # 当前节点的偏导数等于1，因为∂self/∂self = 1。这是反向传播算法的起点\n",
    "        cg_grad = {self: 1}\n",
    "        # 为了计算每个节点的偏导数，需要使用拓扑排序的倒序来遍历计算图\n",
    "        ordered = reversed(_topological_order())\n",
    "        re = []\n",
    "        for node in ordered:\n",
    "            _compute_grad_of_prevs(node)\n",
    "            # 作图需要，实际上对计算没有作用\n",
    "            if fn is not None:\n",
    "                re.append(fn(self, 'backward'))\n",
    "        return re\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "\n",
    "def _get_node_attr(node, direction='forward'):\n",
    "    '''\n",
    "    节点的属性\n",
    "    '''\n",
    "    node_type = _get_node_type(node)\n",
    "    # 设置字体\n",
    "    res = {'fontname': 'Menlo'}\n",
    "    def _forward_attr():\n",
    "        if node_type == 'param':\n",
    "            node_text = f'{{ grad=None | value={node.value:.2f} | {node.label}}}'\n",
    "            res.update(\n",
    "                dict(label=node_text, shape='record', fontsize='10', fillcolor='lightgreen', style='filled, bold'))\n",
    "            return res\n",
    "        elif node_type == 'computation':\n",
    "            node_text = f'{{ grad=None | value={node.value:.2f} | {node.op}}}'\n",
    "            res.update(\n",
    "                dict(label=node_text, shape='record', fontsize='10', fillcolor='gray94', style='filled, rounded'))\n",
    "            return res\n",
    "        elif node_type == 'input':\n",
    "            if node.label == '':\n",
    "                node_text = f'input={node.value:.2f}'\n",
    "            else:\n",
    "                node_text = f'{node.label}={node.value:.2f}'\n",
    "            res.update(dict(label=node_text, shape='oval', fontsize='10'))\n",
    "            return res\n",
    "    \n",
    "    def _backward_attr():\n",
    "        attr = _forward_attr()\n",
    "        attr['label'] = attr['label'].replace('grad=None', f'grad={node.grad:.2f}')\n",
    "        if not node.requires_grad:\n",
    "            attr['style'] = 'dashed'\n",
    "        # 为了作图美观\n",
    "        # 如果向后扩散（反向传播）的梯度等于0，或者扩散给不需要梯度的节点，那么该节点用虚线表示\n",
    "        grad_back = [v if k.requires_grad else 0 for (k, v) in node.back_prop.items()]\n",
    "        if len(grad_back) > 0 and sum(grad_back) == 0:\n",
    "            attr['style'] = 'dashed'\n",
    "        return attr \n",
    "    \n",
    "    if direction == 'forward':\n",
    "        return _forward_attr()\n",
    "    else:\n",
    "        return _backward_attr()\n",
    "    \n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def _get_node_type(node):\n",
    "    '''\n",
    "    决定节点的类型，计算节点、参数以及输入数据\n",
    "    '''\n",
    "    if node.op is not None:\n",
    "        return 'computation'\n",
    "    if node.requires_grad:\n",
    "        return 'param'\n",
    "    return 'input'\n",
    "\n",
    "\n",
    "def _trace(root):\n",
    "    '''\n",
    "    遍历图中的所有点和边\n",
    "    '''\n",
    "    nodes, edges = set(), set()\n",
    "    def _build(v):\n",
    "        if v not in nodes:\n",
    "            nodes.add(v)\n",
    "            for prev in v.prevs:\n",
    "                edges.add((prev, v))\n",
    "                _build(prev)\n",
    "    _build(root)\n",
    "    return nodes, edges\n",
    "\n",
    "\n",
    "def _draw_node(graph, node, direction='forward'):\n",
    "    '''\n",
    "    画节点\n",
    "    '''\n",
    "    node_attr = _get_node_attr(node, direction)\n",
    "    uid = str(id(node)) + direction\n",
    "    graph.node(name=uid, **node_attr)\n",
    "\n",
    "\n",
    "def _draw_edge(graph, n1, n2, direction='forward'):\n",
    "    '''\n",
    "    画边\n",
    "    '''\n",
    "    uid1 = str(id(n1)) + direction\n",
    "    uid2 = str(id(n2)) + direction\n",
    "    def _draw_back_edge():\n",
    "        if n1.requires_grad and n2.requires_grad:\n",
    "            grad = n2.back_prop.get(n1, None)\n",
    "            if grad is None:\n",
    "                graph.edge(uid2, uid1, arrowhead='none', color='deepskyblue')   \n",
    "            elif grad == 0:\n",
    "                graph.edge(uid2, uid1, style='dashed', label=f'{grad:.2f}', color='deepskyblue', fontname='Menlo')\n",
    "            else:\n",
    "                graph.edge(uid2, uid1, label=f'{grad:.2f}', color='deepskyblue', fontname='Menlo')\n",
    "        else:\n",
    "            graph.edge(uid2, uid1, style='dashed', arrowhead='none', color='deepskyblue')\n",
    "\n",
    "    if direction == 'forward':\n",
    "        graph.edge(uid1, uid2)\n",
    "    elif direction == 'backward':\n",
    "        _draw_back_edge()\n",
    "    else:\n",
    "        _draw_back_edge()\n",
    "        graph.edge(uid1, uid2)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def draw_graph(root, direction='forward'):\n",
    "    '''\n",
    "    图形化展示由root为顶点的计算图\n",
    "    参数\n",
    "    ----\n",
    "    root ：Scalar，计算图的顶点\n",
    "    direction ：str，向前传播（forward）或者反向传播（backward）\n",
    "    返回\n",
    "    ----\n",
    "    re ：Digraph，计算图\n",
    "    '''\n",
    "    nodes, edges = _trace(root)\n",
    "    rankdir = 'BT' if direction == 'forward' else 'TB'\n",
    "    graph = Digraph(format='svg', graph_attr={'rankdir': rankdir})\n",
    "    for item in nodes:\n",
    "        _draw_node(graph, item, direction)\n",
    "    for n1, n2 in edges:\n",
    "        _draw_edge(graph, n1, n2, direction)\n",
    "    return graph"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 用于定义线性模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mse(errors):\n",
    "    '''\n",
    "    计算均方误差\n",
    "    '''\n",
    "    n = len(errors)\n",
    "    wrt = {}\n",
    "    value = 0.0\n",
    "    requires_grad = False\n",
    "    for item in errors:\n",
    "        value += item.value ** 2 / n\n",
    "        wrt[item] = 2 / n * item.value\n",
    "        requires_grad = requires_grad or item.requires_grad\n",
    "    output = Scalar(value, errors, 'mse')\n",
    "    output.requires_grad=requires_grad\n",
    "    output.grad_wrt = wrt\n",
    "    return output\n",
    "\n",
    "\n",
    "class Linear:\n",
    "    \n",
    "    def __init__(self):\n",
    "        '''\n",
    "        定义线性回归模型的参数：a, b\n",
    "        '''\n",
    "        self.a = Scalar(0.0, label='a')\n",
    "        self.b = Scalar(0.0, label='b')\n",
    "\n",
    "    def forward(self, x):\n",
    "        '''\n",
    "        根据当前的参数估计值，得到模型的预测结果\n",
    "        '''\n",
    "        return self.a * x + self.b\n",
    "    \n",
    "    def error(self, x, y):\n",
    "        '''\n",
    "        当前数据的模型误差\n",
    "        '''\n",
    "        return y - self.forward(x)\n",
    "\n",
    "    def string(self):\n",
    "        '''\n",
    "        输出当前模型的结果\n",
    "        '''\n",
    "        return f'y = {self.a.value:.2f} * x + {self.b.value:.2f}'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = torch.linspace(100,300,200)\n",
    "x = (x - x.mean()) / x.std()\n",
    "epsilon = torch.randn(x.shape)\n",
    "y = 10 * x + 5 + epsilon"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x20b8401d950>]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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igrhoYceElJTU4WKigqhoqJCcLlcyjmzZs0S3njjDUEQBMHlcgnf+973hB07dgg1NTXCli1bhJUrVwr5+fmC0+mM1cc4Z2P9HgRBEH72s58JFotFeOONN4SDBw8KX/rSl4Tc3NxJ+z1cddVVwoIFC4SdO3cKO3fuFObPny9cffXVYeck2u/CK6+8Iuh0OuG5554Tjhw5Itx9991CSkqKcPr0aUEQBOG+++4Tbr31VuX86upqITk5Wfjud78rHDlyRHjuuecEnU4nvPbaa7H6COdsrN/BL3/5S+HNN98UTpw4IRw6dEi47777BADC66+/HquPMC5cLpfy3z0A4fHHHxcqKiqE2tpaQRCmxu/CWL+DRP1diIRhJcbWr18vABhy27Jli3IOAOH5558XBEEQ+vr6hLVr1wpZWVmCTqcTioqKhPXr1wt1dXWx+QDjZKzfgyCI05cffPBBwWazCQaDQbj44ouFgwcPTvzFj5OOjg7hlltuEUwmk2AymYRbbrllyJTERPxd+PWvfy0UFxcLer1eWLx4sbBt2zblsfXr1wuXXHJJ2Plbt24VFi1aJOj1eqGkpER4+umnJ/iKx99YvoOf//znwrRp0wSj0SikpaUJF154obBp06YYXPX4kqfhDr6tX79eEISp8bsw1u8gUX8XIlEJgtSRRERERBSHOHWZiIiI4hrDChEREcU1hhUiIiKKawwrREREFNcYVoiIiCiuMawQERFRXGNYISIiorjGsEJERERxjWGFiIiI4hrDChEREcU1hhUiIiKKawwrREREFNf+PxILsuHjC4zDAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(x,y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "y = 2.83 * x + -1.92\n",
      "y = 2.67 * x + -1.70\n",
      "y = 2.41 * x + -0.83\n",
      "y = 2.75 * x + 0.60\n",
      "y = 4.29 * x + 2.58\n",
      "y = 6.90 * x + 4.55\n",
      "y = 8.80 * x + 5.69\n",
      "y = 9.14 * x + 5.39\n",
      "y = 9.18 * x + 5.29\n",
      "y = 9.18 * x + 5.19\n",
      "y = 9.24 * x + 5.27\n",
      "y = 9.36 * x + 5.38\n",
      "y = 9.56 * x + 5.49\n",
      "y = 9.80 * x + 5.30\n",
      "y = 9.76 * x + 5.34\n",
      "y = 9.76 * x + 5.23\n",
      "y = 9.76 * x + 5.21\n",
      "y = 9.76 * x + 5.21\n",
      "y = 9.79 * x + 5.22\n",
      "y = 9.93 * x + 5.12\n"
     ]
    }
   ],
   "source": [
    "model = Linear()\n",
    "\n",
    "batch_size = 32\n",
    "lr = 0.1\n",
    "\n",
    "for t in range(20):\n",
    "    ix = (t * batch_size) % len(x)\n",
    "    xx = x[ix:ix+batch_size]\n",
    "    yy = y[ix:ix+batch_size]\n",
    "    loss = mse([model.error(_x,_y) for _x,_y in zip(xx,yy)])\n",
    "    loss.backward()\n",
    "    model.a -= lr * model.a.grad\n",
    "    model.b -= lr * model.b.grad\n",
    "    model.a.grad = 0\n",
    "    model.b.grad = 0\n",
    "\n",
    "    print(model.string())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 计算图膨胀"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "nvgpu",
   "language": "python",
   "name": "nvgpu"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
